import ast
import datetime
import functools
import inspect
import traceback
from inspect import isawaitable
from typing import Callable

from mg_app_framework import get_logger
from tornado.ioloop import IOLoop


class FunctionUtils:
    # 将字符串作为函数执行
    @classmethod
    async def call_string_function(cls, func_str: str, *args, **kwargs):
        f = cls.get_callable_function(func_str)
        try:
            return await cls.call_function(f, *args, **kwargs)
        except Exception:
            get_logger().error(
                f'call function error, args -> {args}, kwargs -> {kwargs} traceback -> {traceback.format_exc()}')
            raise

    @classmethod
    async def call_function(cls, func: Callable, *args, **kwargs):
        kwargs.setdefault('get_logger', get_logger)
        res = func(*args, **kwargs)
        if isawaitable(res):
            return await res
        else:
            return res

    @staticmethod
    def get_callable_function(func_str) -> Callable:
        node = ast.parse(func_str)
        loc = dict()
        exec(compile(node, filename="<ast>", mode="exec"), globals(), loc)
        for v in loc.values():
            if inspect.isfunction(v):
                return v
        raise Exception("no function")

    @classmethod
    async def my_run_in_executor(cls, f, *args, **kwargs):
        f = functools.partial(f, *args, **kwargs)
        return await IOLoop.current().run_in_executor(None, f)

    @staticmethod
    def cost_time_decorator(method):
        @functools.wraps(method)
        async def wrapper(self, *args, **kwargs):
            start = datetime.datetime.now()
            res = await method(self, *args, **kwargs)
            get_logger().info(
                f'{self.__class__.__name__}.{method.__name__} cost {(datetime.datetime.now() - start).microseconds / 1000} m'
                f's')
            return res

        return wrapper

    @staticmethod
    def log_exception(method):
        @functools.wraps(method)
        async def wrapper(self, *args, **kwargs):
            try:
                return await method(self, *args, **kwargs)
            except Exception:
                get_logger().error(traceback.format_exc())
                raise

        return wrapper
